/*
 * Copyright (c) 2017-2019, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2019/11/04
 *
 * History:
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>

#include "osal_log.h"
#include "osal_common.h"
#include "osal_prop.h"
#include "mpp_camera.h"
#include "mpp_audio.h"
#include "mpp_thumb.h"

#ifdef ENABLE_LIVE
#include "livesink.h"
#endif

#define LIVE_STREAM_CNT     2
#define STREAM_AAC_INDEX    0
static int g_venc_type[LIVE_STREAM_CNT] = {AV_ENC_TYPE_H264, AV_ENC_TYPE_H265};
static int g_aenc_type[LIVE_STREAM_CNT] = {AV_ENC_TYPE_AAC, AV_ENC_TYPE_MP3};
static int g_stream_port[LIVE_STREAM_CNT] = {8554, 554};

#define DEFAULT_SAMPLE_RATE 16000

typedef struct _ldcam_ctx_t
{
    _handle_t           h_camera;
    int                 i_camid;

    int                 b_test_case;
    int                 b_recording;
    int                 b_rec_loop;
    int                 b_venc_flag[MULTI_VENC_CNT];
    int                 b_yuv_flag[MULTI_YUV_CNT];
    int                 b_osd_flag[OSD_CNT];
    int                 i_zoom_ratio;
    int                 i_color_mode; // 0: colorful, 1: black&white

    char *              p_save_path;

    _handle_t           h_audio;
    mpp_audio_config_t  audio_config;
    int                 b_aenc_flag[MULTI_AENC_CNT];
    int                 b_pcm_flag;

    int8_t              i_dbg_save_aenc;
    int8_t              i_dbg_save_pcm;
    FILE *              fd_dbg_aenc[MULTI_AENC_CNT];
    FILE *              fd_dbg_pcm;

#ifdef ENABLE_LIVE
    int                 b_flag_live[LIVE_STREAM_CNT];
    livesink_handle     h_live[LIVE_STREAM_CNT];
#endif

    pthread_t           tid_input;

    pthread_mutex_t     mutex_pic;
    pthread_cond_t      cond_pic;

    camera_config_t     camera_config;
    preview_config_t    preview_config;
    picture_config_t    picture_config;
    record_config_t     record_config;

    int                 i_ui_show;

    char                video_path_last[64];
    char                video_path_cur[64];
    int                 i_thumb_w;
    int                 i_thumb_h;
} ldcam_ctx_t;

#include "camera_test_cases.h"

static void get_current_date_time(char *buf, int max_size)
{
    time_t t;
    struct tm *tm_t;
    time(&t);
    tm_t = localtime(&t);
    snprintf(buf, max_size, "%4d%02d%02d_%02d%02d%02d",
            tm_t->tm_year+1900, tm_t->tm_mon+1, tm_t->tm_mday,
            tm_t->tm_hour, tm_t->tm_min, tm_t->tm_sec);
}

static void calculate_crop(rect_t * rect, int ratio, int width, int height)
{
    if (ratio < 100) {
        ratio = 100;
    }
    if (ratio > 800) {
        ratio = 800;
    }
    rect->left      = ALIGN_16((width - (width * 100) / ratio)/2);
    rect->top       = ALIGN_8((height - (height * 100) / ratio)/2);
    rect->width     = ALIGN_16((width * 100) / ratio);
    rect->height    = ALIGN_8((height * 100) / ratio);

    logd("crop: [%d, %d, %d, %d]", rect->left, rect->top, rect->width, rect->height);
}

static void post_takepicture_end(ldcam_ctx_t * p_ctx)
{
    pthread_mutex_lock(&p_ctx->mutex_pic);
    pthread_cond_signal(&p_ctx->cond_pic);
    pthread_mutex_unlock(&p_ctx->mutex_pic);
}

static void wait_takepicture_end(ldcam_ctx_t * p_ctx)
{
    pthread_mutex_lock(&p_ctx->mutex_pic);
    pthread_cond_wait(&p_ctx->cond_pic, &p_ctx->mutex_pic);
    pthread_mutex_unlock(&p_ctx->mutex_pic);
}

static int take_picture(ldcam_ctx_t * p_ctx)
{
    logd("take_picture");
    _handle_t camera = p_ctx->h_camera;
    char pic_path[256];
    char date_time[64];
    mpp_cam_picture_channel_prepare(camera);
    mpp_cam_picture_set_params(camera, &p_ctx->picture_config);

    get_current_date_time(date_time, 64);
    sprintf(pic_path, "%s/%s.jpg", p_ctx->p_save_path, date_time);
    logd("picture path: %s", pic_path);
    mpp_cam_picture_taking(camera, pic_path);
    wait_takepicture_end(p_ctx);
    mpp_cam_picture_channel_destroy(camera);

    return 0;
}

static int do_recording(ldcam_ctx_t * p_ctx)
{
    _handle_t camera = p_ctx->h_camera;
    char date_time[64];

    logd("recording status: %d ", p_ctx->b_recording);
    if (p_ctx->b_recording)
    {
        logd("stop recording");
        p_ctx->b_recording = 0;
        mpp_cam_record_stop(camera);
    }
    else
    {
        logd("start recording");
        char video_path[64];
        mpp_cam_record_set_params(camera, &p_ctx->record_config);
        get_current_date_time(date_time, 128);
        sprintf(video_path, "%s/%s_%dx%d_%dfps_%s.%s", p_ctx->p_save_path, date_time,
                p_ctx->record_config.venc_width,
                p_ctx->record_config.venc_height,
                p_ctx->record_config.framerate,
                (p_ctx->record_config.venc_type == AV_ENC_TYPE_H264) ? "h264" : "h265",
                (p_ctx->record_config.mux_type == AV_MUX_TYPE_MP4) ? "mp4" : "ts");
        logd("video path: %s", video_path);
        strcpy(p_ctx->video_path_last, video_path);
        mpp_cam_record_start(camera, video_path);
        p_ctx->b_recording = 1;
    }

    return 0;
}

static int start_cb_venc(ldcam_ctx_t * p_ctx, int index)
{
    int ret = 0;
    _handle_t camera = p_ctx->h_camera;
    /*
     * video encoding parameters
     */
    venc_config_t venc_cfg;
    memset(&venc_cfg, 0, sizeof(venc_config_t));
    if (index == 0)
    {
        venc_cfg.venc_type      = g_venc_type[index];
        venc_cfg.venc_width     = 1920;
        venc_cfg.venc_height    = 1080;
        venc_cfg.bitrate        = 2*1024*1024;
    }
    else
    {
        venc_cfg.venc_type      = g_venc_type[index];
        venc_cfg.venc_width     = 1920;
        venc_cfg.venc_height    = 1080;
        venc_cfg.bitrate        = 1*1024*1024;
    }
    venc_cfg.framerate          = p_ctx->camera_config.framerate;    // same as the capture framerate
    venc_cfg.gop                = 30;
    venc_cfg.venc_rotate        = 0;
    venc_cfg.venc_mirror        = 0;
    venc_cfg.mux_type           = AV_MUX_TYPE_NONE;
    venc_cfg.rc_mode            = 0;
    venc_cfg.min_qp             = 5;
    venc_cfg.max_qp             = 45;
    venc_cfg.profile            = 1;
    venc_cfg.iframe_attach_spspps = 1;
    mpp_cam_venc_set_params(camera, index, &venc_cfg);

    ret = mpp_cam_venc_start(camera, index);
    if (ret < 0)
    {
        loge("mpp_cam_start_cb_venc failed");
        return -1;
    }
    return 0;
}

static void stop_cb_venc(ldcam_ctx_t * p_ctx, int index)
{
    _handle_t camera = p_ctx->h_camera;
    mpp_cam_venc_stop(camera, index);
}

static int camera_on_input_key(ldcam_ctx_t * p_ctx)
{
    signed char key_val;
    _handle_t h_camera = p_ctx->h_camera;
    _handle_t h_audio = p_ctx->h_audio;
    int ret = wait_input_char(&key_val, 500000);    // 500ms time out
    if (ret < 0)
    {
        return -1;
    }
    else if (ret == 0)
    {
        return 0;
    }

    if (key_val > 0 && key_val)
    {
        if (key_val == '\n')
        {
            printf("\n");
        }
        else
        {
            printf("********* key_val: %c ********** \n", key_val);
        }

        if (key_val == 'q' || key_val == 'Q')
        {
            return -1;
        }
        else if (key_val == 'r' || key_val == 'R')
        {
            do_recording(p_ctx);
            return 0;
        }
        else if (key_val == 'p' || key_val == 'P')
        {
            take_picture(p_ctx);
            return 0;
        }
        else if (key_val == 'v' || key_val == 'V')
        {
            int index = (key_val == 'v') ? 0 : 1;
            if (p_ctx->b_venc_flag[index])
            {
                p_ctx->b_venc_flag[index] = 0;
                stop_cb_venc(p_ctx, index);
            }
            else
            {
                p_ctx->b_venc_flag[index] = 1;
                start_cb_venc(p_ctx, index);
            }
            return 0;
        }
        else if (key_val == 'y' || key_val == 'Y')
        {
            int index = (key_val == 'y') ? 0 : 1;
            if (p_ctx->b_yuv_flag[index])
            {
                p_ctx->b_yuv_flag[index] = 0;
                mpp_cam_yuv_stop(h_camera, index);
            }
            else
            {
                p_ctx->b_yuv_flag[index] = 1;
                mpp_cam_yuv_start(h_camera, index);
            }
            return 0;
        }
        else if (key_val == 'a' || key_val == 'A')
        {
            /*
             * callback audio stream 0/1
             */
            int index = (key_val == 'a') ? 0 : 1;
            if (p_ctx->b_aenc_flag[index])
            {
                p_ctx->b_aenc_flag[index] = 0;
                mpp_audio_aenc_stop(h_audio, index);
            }
            else
            {
                mpp_aenc_config_t aenc_cfg = {0};
                aenc_cfg.aenc_type = g_aenc_type[index];
                aenc_cfg.mute = 0;
                mpp_audio_aenc_set_params(h_audio, index, &aenc_cfg);
                mpp_audio_aenc_start(h_audio, index);
                p_ctx->b_aenc_flag[index] = 1;
            }
            return 0;
        }
        else if (key_val == 'm')
        {
            /*
             * callback audio pcm
             */
            if (p_ctx->b_pcm_flag)
            {
                p_ctx->b_pcm_flag = 0;
                mpp_audio_pcm_stop(h_audio);
            }
            else
            {
                mpp_audio_pcm_start(h_audio);
                p_ctx->b_pcm_flag = 1;
            }
            return 0;
        }
        else if (key_val == 'Z')    // zoom in
        {
            p_ctx->i_zoom_ratio += 10;
            if (p_ctx->i_zoom_ratio < 100) {
                p_ctx->i_zoom_ratio = 100;
            }
            if (p_ctx->i_zoom_ratio > 400) {
                p_ctx->i_zoom_ratio = 400;
            }
            rect_t crop = {0};
            calculate_crop(&crop, p_ctx->i_zoom_ratio,
                p_ctx->camera_config.cap_width,
                p_ctx->camera_config.cap_height);
            mpp_cam_record_set_crop(h_camera, &crop);
            return 0;
        }
        else if (key_val == 'z')    // zoom out
        {
            p_ctx->i_zoom_ratio -= 10;
            if (p_ctx->i_zoom_ratio < 100) {
                p_ctx->i_zoom_ratio = 100;
            }
            if (p_ctx->i_zoom_ratio > 400) {
                p_ctx->i_zoom_ratio = 400;
            }
            rect_t crop = {0};
            calculate_crop(&crop, p_ctx->i_zoom_ratio,
                p_ctx->camera_config.cap_width,
                p_ctx->camera_config.cap_height);
            mpp_cam_record_set_crop(h_camera, &crop);
            return 0;
        }
        else if (key_val == 'b')    // black & white
        {
            p_ctx->i_color_mode = p_ctx->i_color_mode == 0 ? 1 : 0;
            mpp_cam_set_color_mode(h_camera, p_ctx->i_color_mode);
            return 0;
        }
        else if (key_val == 'w' || key_val == 'W')    // osd
        {
            int index = (key_val == 'w') ? 0 : 1;
            if (p_ctx->b_osd_flag[index])
            {
                p_ctx->b_osd_flag[index] = 0;
                mpp_cam_osd_disable(h_camera, index);
            }
            else
            {
                mpp_osd_config_t osd_cfg = {0};
                if (index == 0) {
                    osd_cfg.osd_type = LD_CAMERA_OSD_TYPE_DATE;
                    osd_cfg.left = 128;
                    osd_cfg.top = 128;
                    osd_cfg.font_size = 32;
                } else {
                    osd_cfg.osd_type = LD_CAMERA_OSD_TYPE_STRING;
                    osd_cfg.data = "Hello, Welcome to lindenis!";
                    osd_cfg.left = 128;
                    osd_cfg.top = 256;
                    osd_cfg.font_size = 64;
                }
                if (!mpp_cam_osd_set_params(h_camera, index, &osd_cfg)) {
                    mpp_cam_osd_enable(h_camera, index);
                    p_ctx->b_osd_flag[index] = 1;
                }
            }
            return 0;
        }
        else if (key_val == 'M')
        {
            int mute = 0;
            if (p_ctx->b_aenc_flag[STREAM_AAC_INDEX])
            {
                mute = mpp_audio_aenc_get_mute(h_audio, STREAM_AAC_INDEX);
                if (mute < 0) {
                    loge("aenc get mute something wrong");
                    return 1;
                }
                logd("aenc set mute: %d", !mute);
                mpp_audio_aenc_set_mute(h_audio, STREAM_AAC_INDEX, !mute);
            }
            if (p_ctx->b_recording)
            {
                mute = mpp_cam_record_get_mute(h_camera);
                if (mute < 0) {
                    loge("record get mute something wrong");
                    return 1;
                }
                logd("record set mute: %d", !mute);
                mpp_cam_record_set_mute(h_camera, !mute);
            }
            return 0;
        }
        else if (key_val == 'h' || key_val == 'H')
        {
            p_ctx->i_ui_show = p_ctx->i_ui_show ? 0 : 1;
            mpp_cam_show_ui(h_camera, p_ctx->i_ui_show);
            return 0;
        }
#ifdef ENABLE_LIVE
        else if (key_val == 's' || key_val == 'S')
        {
            int index = (key_val == 's') ? 0 : 1;
            /*
             * callback audio pcm
             */
            if (p_ctx->b_flag_live[index])
            {
                p_ctx->b_flag_live[index] = 0;
                livesink_deinit(p_ctx->h_live[index]);
                livesink_destroy(p_ctx->h_live[index]);
            }
            else
            {
                p_ctx->h_live[index] = livesink_create();
                if (!p_ctx->h_live[index]) {
                    return -1;
                }
                livesink_config_t sink_config = {0};
                sink_config.ip = "0.0.0.0";
                sink_config.port = g_stream_port[index];
                sink_config.name = "live";
                sink_config.aenc_type = g_aenc_type[index];
                sink_config.channels = p_ctx->audio_config.channels;
                sink_config.sample_rate = p_ctx->audio_config.sample_rate;
                sink_config.venc_type = g_venc_type[index];
                ret = livesink_init(p_ctx->h_live[index], &sink_config);
                if (ret) {
                    return -1;
                }
                p_ctx->b_flag_live[index] = 1;
            }
            return 0;
        }
#endif
    }

    return 0;
}

static void * camera_input_key_thread(void *param)
{
    ldcam_ctx_t * p_ctx = (ldcam_ctx_t *)param;

    while(1)
    {
        if (camera_on_input_key(p_ctx))
            break;
    }

    logd("input_thread exit ");

    return 0;
}

static int lindaudio_cb(void *user, int msg, void * ext1, void * ext2)
{
    ldcam_ctx_t * p_ctx = (ldcam_ctx_t *)user;
    switch (msg) {
        case LD_AUDIO_MSG_PCM:
        {
            logv("message: LD_AUDIO_MSG_PCM");
            static int pcm_cnt = 0;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            logv("pcm_cnt: %d, cb pcm data: %p, size: %d, pts: %lld, rate: %d, channels: %d, bits: %d, nb: %d",
                pcm_cnt++, p_frame->data[0], p_frame->size[0], p_frame->pts,
                p_frame->sample_rate, p_frame->channels, p_frame->bits, p_frame->nb_samples);

            if (p_ctx->fd_dbg_pcm)
            {
                fwrite(p_frame->data[0], p_frame->size[0], 1, p_ctx->fd_dbg_pcm);
            }
            break;
        }
        case LD_AUDIO_MSG_AENC:
        {
            logv("message: LD_AUDIO_MSG_AENC");
            static int aenc_cnt = 0;
            int index = *(int*)ext2;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            logv("aenc_cnt: %d, index: %d, cb aenc data: %p, size: %d, pts: %lld",
                aenc_cnt++, index, p_frame->data[0], p_frame->size[0], p_frame->pts);
            if (p_ctx->fd_dbg_aenc[index])
            {
                fwrite(p_frame->data[0], p_frame->size[0], 1, p_ctx->fd_dbg_aenc[index]);
            }
#ifdef ENABLE_LIVE
            if (g_aenc_type[index] == AV_ENC_TYPE_AAC)
            {
                // live stream only support aac audio codec
                int i = 0;
                for (i = 0; i < LIVE_STREAM_CNT; i++)
                {
                    if (p_ctx->b_flag_live[i]) {
                        livesink_write2(p_ctx->h_live[i], p_frame);
                    }
                }
            }
#endif
            break;
        }
        default:
            loge("lindaudio_cb unknown msg type: %d", msg);
            break;
    }

    return 0;
}

static int lindcamera_cb(void *user, int msg, void * ext1, void * ext2)
{
    ldcam_ctx_t * p_ctx = (ldcam_ctx_t *)user;
    switch (msg) {
        case LD_CAMERA_MSG_TAKE_PICTURE_END:
            logd("message: LD_CAMERA_MSG_TAKE_PICTURE_END");
            post_takepicture_end(p_ctx);
            break;
        case LD_CAMERA_MSG_CB_ES:
        {
            logv("message: LD_CAMERA_MSG_CB_ES");
            static int venc_cnt = 0;
            int index = *(int*)ext2;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            logv("venc_cnt: %d, index: %d, cb es data: %p, size: %d",
                venc_cnt++, index, p_frame->data[0], p_frame->size[0]);

#ifdef ENABLE_LIVE
            if (p_ctx->b_flag_live[index])
            {
                livesink_write2(p_ctx->h_live[index], p_frame);
            }
#endif
            break;
        }
        case LD_CAMERA_MSG_CB_YUV:
        {
            logv("message: LD_CAMERA_MSG_CB_ES");
            static int yuv_cnt = 0;
            int index = *(int*)ext2;
            av_frame2_t * p_frame = (av_frame2_t *)ext1;
            printf("yuv_cnt: %d, index: %d, cb yuv data: %p, %p, size: %dx%d\n",
                yuv_cnt++, index, p_frame->data[0], p_frame->data[1],
                p_frame->width, p_frame->height);
            break;
        }
        case LD_CAMERA_MSG_RECORD_DONE:
        {
            logd("message: LD_CAMERA_MSG_RECORD_DONE");
            /*
             * a new record file done, should do something
             */
            char * video_path = p_ctx->video_path_last;
            int max_size = p_ctx->i_thumb_w * p_ctx->i_thumb_h * 3;
            char * thumb_buf = (char *)malloc(max_size);
            if (!thumb_buf) {
                loge("oom");
                return -1;
            }
            int size = mpp_thumb_video(video_path, p_ctx->i_thumb_w, p_ctx->i_thumb_h, thumb_buf, max_size);
            if (size > 0) {
                save_file("/mnt/UDISK/1.thumb.jpg", thumb_buf, size);
            } else {
                loge("get video thumbnail failed");
            }
            free(thumb_buf);
            if (p_ctx->b_rec_loop) {
                strcpy(p_ctx->video_path_last, p_ctx->video_path_cur);
            }
            break;
        }
        case LD_CAMERA_MSG_RECORD_NEXT_FD:
        {
            char video_path[64];
            char date_time[64];
            if (!p_ctx->b_rec_loop) {
                logd("disabled loop record, ignore");
                return 0;
            }
            get_current_date_time(date_time, 128);
            sprintf(video_path, "%s/%s_%dx%d_%dfps_%s.%s", p_ctx->p_save_path, date_time,
                    p_ctx->record_config.venc_width,
                    p_ctx->record_config.venc_height,
                    p_ctx->record_config.framerate,
                    (p_ctx->record_config.venc_type == AV_ENC_TYPE_H264) ? "h264" : "h265",
                    (p_ctx->record_config.mux_type == AV_MUX_TYPE_MP4) ? "mp4" : "ts");
            int fd = open(video_path, O_RDWR | O_CREAT, 0666);
            if (fd < 0) {
                loge("fail to open %s", video_path);
                return -1;
            }
            mpp_cam_record_switch_next_fd(p_ctx->h_camera, fd);
            strcpy(p_ctx->video_path_cur, video_path);
            close(fd);
            break;
        }
        default:
            loge("lindcamera_cb unknown msg type: %d", msg);
            break;
    }

    return 0;
}

static void do_test_case(ldcam_ctx_t * p_ctx)
{
    // start recording
    do_recording(p_ctx);
    sleep(3);
    // take picture
    take_picture(p_ctx);
    sleep(2);
    // stop recording
    do_recording(p_ctx);
}

int sample_lindcamera(ldcam_ctx_t * p_ctx)
{
    int ret = 0;
    int i = 0;

    /**
     * get dbg flag from "/etc/lindsdk.ini"
     */
    p_ctx->i_dbg_save_aenc = ldprop_get_boolean(SECTION_DEBUG, "dbg_save_audio_es", 0);
    p_ctx->i_dbg_save_pcm = ldprop_get_boolean(SECTION_DEBUG, "dbg_save_audio_pcm", 0);
    if (p_ctx->i_dbg_save_aenc)
    {
        char file_path[256] = {0};
        for (i = 0; i < MULTI_AENC_CNT; i++)
        {
            sprintf(file_path, "%s/audio%d.es", ldprop_get_string(SECTION_DEBUG, "dir_dgb_data", "/tmp"), i);
            p_ctx->fd_dbg_aenc[i] = fopen(file_path, "wb");
            if (NULL == p_ctx->fd_dbg_aenc[i])
            {
                loge("Open %s file error", file_path);
                return -1;
            }
        }
    }
    if (p_ctx->i_dbg_save_pcm)
    {
        char file_path[256] = {0};
        sprintf(file_path, "%s/audio.pcm", ldprop_get_string(SECTION_DEBUG, "dir_dgb_data", "/tmp"));
        p_ctx->fd_dbg_pcm = fopen(file_path, "wb");
        if (NULL == p_ctx->fd_dbg_pcm)
        {
            loge("Open %s file error", file_path);
            return -1;
        }
    }

    /**
     * create camera
     */
    p_ctx->h_camera = mpp_cam_create(p_ctx->i_camid);
    if (p_ctx->h_camera == (_handle_t)NULL)
    {
        loge("mpp_cam_create failed");
        return -1;
    }
    mpp_cam_set_params(p_ctx->h_camera, &p_ctx->camera_config);

    /**
     * create audio source
     */
    p_ctx->audio_config.msg_cb = lindaudio_cb;
    p_ctx->audio_config.user = p_ctx;
    p_ctx->audio_config.channels = 1;
    p_ctx->audio_config.sample_rate = DEFAULT_SAMPLE_RATE;
    p_ctx->audio_config.bits = 16;
    p_ctx->h_audio = mpp_audio_create(0, &p_ctx->audio_config);
    if (p_ctx->h_audio == (_handle_t)NULL)
    {
        loge("mpp_audio_create failed");
        return -1;
    }

    pthread_mutex_init(&p_ctx->mutex_pic, NULL);
    pthread_cond_init(&p_ctx->cond_pic, NULL);

    p_ctx->i_zoom_ratio = 100;

    /**
     * start preview
     */
    mpp_cam_preview_set_params(p_ctx->h_camera, &p_ctx->preview_config);
    mpp_cam_preview_start2(p_ctx->h_camera);

    if (p_ctx->b_test_case)
    {
        do_test_case(p_ctx);
    }
    else
    {
        ret = pthread_create(&p_ctx->tid_input, NULL, camera_input_key_thread, (void *)p_ctx);
        if (ret) {
            loge("pthread_create msg thread fail! ret: %d ", ret);
            goto __start_failed__;
        }

        pthread_join(p_ctx->tid_input, 0);
    }

__start_failed__:

    /**
     * stop recording if needed
     */
    if (p_ctx->b_recording == 1)
    {
        p_ctx->b_recording = 0;
        mpp_cam_record_stop(p_ctx->h_camera);
    }

    /**
     * stop preview
     */
    mpp_cam_preview_stop2(p_ctx->h_camera);

    pthread_mutex_destroy(&p_ctx->mutex_pic);
    pthread_cond_destroy(&p_ctx->cond_pic);

    /**
     * destroy camera
     */
    mpp_cam_destroy(p_ctx->h_camera);

    /**
     * destroy audio source
     */
    mpp_audio_destroy(p_ctx->h_audio);

    /**
     * clean dbg resources if needed
     */
    if (NULL != p_ctx->fd_dbg_pcm)
    {
        fclose(p_ctx->fd_dbg_pcm);
        p_ctx->fd_dbg_pcm = NULL;
    }
    for (i = 0; i < MULTI_AENC_CNT; i++)
    {
        if (NULL != p_ctx->fd_dbg_aenc[i])
        {
            fclose(p_ctx->fd_dbg_aenc[i]);
            p_ctx->fd_dbg_aenc[i] = NULL;
        }
    }

    return 0;
}

static void print_usage()
{
    printf("\033[32m    Usage: \033[0m\n");
    printf("\033[32m        lindcamera <options>\033[0m\n");
    printf("\033[32m    other options parameters:\033[0m\n");
    printf("\033[32m            -id [camera_id]              : set the camera id to be opened \033[0m\n");
    printf("\033[32m            -path [save_path]            : set the path to save picture or video file, default '/mnt/UDISK' \033[0m\n");
    printf("\033[32m            -s  | -cap_size [w h]        : set video capture size, default 1920x1080 \033[0m\n");
    printf("\033[32m            -ss | -cap_size_sub [w h]    : set sub video capture size, default 1280x720 \033[0m\n");
    printf("\033[32m            -f  | -framerate [framerate] : set video framerate, default 30fps \033[0m\n");
    printf("\033[32m            -vt | -venc_type [h264/h265] : set video encode type, default h.264 \033[0m\n");
    printf("\033[32m            -vs | -venc_size [w h]       : set video encode size, default 1920x1080 \033[0m\n");
    printf("\033[32m            -vb | -bitrate [bitrate]     : set video encode bitrate in unit bps, default 5*1024*1024bps or 5Mbps \033[0m\n");
    printf("\033[32m            -ts | -thumb_size [w h]      : set video thumbnail size, default 480x272 \033[0m\n");
    printf("\033[32m            -ps | -pic_size [w h]        : set picture size, default 1920x1080 \033[0m\n");
    printf("\033[32m            -dp | -disp_pos [x y w h]    : set the video display position on the screen \033[0m\n");
    printf("\033[32m            -dr | -disp_rotate [rotate]  : set the video display rotate degree, '0, 90, 180, 270' \033[0m\n");
    printf("\033[32m            -dz | -disp_zorder [z-order] : set the z-order of the display layer, range: [0, 16], default: 5 \033[0m\n");
    printf("\033[32m            -dc | -disp_channel [channel]: set the channel of the display layer, range: [0, 1], default: 0 \033[0m\n");
    printf("\033[32m            -ar | -sample_rate [rate]    : set the audio capture sample rate \033[0m\n");
    printf("\033[32m            -mt | -mux_type [mp4/ts]     : set video recorder muxer type, default 'mp4' \033[0m\n");
    printf("\033[32m            -md | -max_duration [time_ms]: set max file duration of a single video recording file, unit: ms, default: 4hr \033[0m\n");
    printf("\033[32m            -ms | -max_size [bytes]      : set max file size of a single video recording file, unit: byte, default: 8GB \033[0m\n");
    printf("\033[32m            -ml | -mux_loop              : enable loop recording, default: disable \033[0m\n");
    printf("\033[32m        such as: lindcamera \033[0m\n");
    printf("\033[32m             or: lindcamera -id 0 -path /tmp "
                                                "-s 3840 2160 -f 30 "
                                                "-ss 1920 1080 "
                                                "-vt h264 "
                                                "-vs 1920 1080 "
                                                "-vb 5000000 "
                                                "-ar 16000 "
                                                "-mt ts "
                                                "-md 300000 "
                                                "-ms 256000000 "
                                                "-ml "
                                                "-ts 480 272 "
                                                "-ps 3840 2160 "
                                                "-dp 0 0 1280 720 "
                                                "-dr 90 \033[0m\n");
    printf("\033[32m        After lindcamera is running, there are some useful command: \033[0m\n");
    printf("\033[32m             'q' or 'Q': exit this app \033[0m\n");
    printf("\033[32m             'r' or 'R': start or stop recording \033[0m\n");
    printf("\033[32m             'p' or 'P': taking picture \033[0m\n");
    printf("\033[32m             'z' or 'Z': zoom out/in \033[0m\n");
    printf("\033[32m             'v' or 'V': callback video es \033[0m\n");
    printf("\033[32m             'y' or 'Y': callback yuv frame \033[0m\n");
    printf("\033[32m             'a' or 'A': callback audio es \033[0m\n");
    printf("\033[32m             'm'       : callback pcm frame \033[0m\n");
    printf("\033[32m             'M'       : set audio encode mute or not \033[0m\n");
    printf("\033[32m             's' or 'S': start a rtsp server \033[0m\n");
}

int main(int argc, char** argv)
{
    int i = 0;
    ldcam_ctx_t context;

    if ((argc == 2)
        && (!strcmp(argv[1], "-h")))
    {
        print_usage();
        return 0;
    }

    if ((argc == 2)
        && (!strcmp(argv[1], "-testcase")))
    {
        for (i = 0; i < g_record_case_cnt; i++)
        {
            logc("--- testcase %d", i);
            g_record_case[i].i_camid = 0;
            g_record_case[i].camera_config.msg_cb = lindcamera_cb;
            g_record_case[i].camera_config.user = (void*)&context;
            g_record_case[i].camera_config.flip = 0;
            g_record_case[i].camera_config.mirror = 0;

            g_record_case[i].preview_config.pos.left = 0;
            g_record_case[i].preview_config.pos.top = 0;
            g_record_case[i].preview_config.pos.width = 1920;
            g_record_case[i].preview_config.pos.height = 1080;

            g_record_case[i].record_config.venc_rotate = 0;
            g_record_case[i].record_config.venc_mirror = 0;
            g_record_case[i].record_config.rc_mode = 0;
            g_record_case[i].record_config.min_qp = 5;
            g_record_case[i].record_config.max_qp = 45;
            g_record_case[i].record_config.profile = 1;

            g_record_case[i].picture_config.quality = 88;
            sample_lindcamera(&g_record_case[i]);
        }
        return 0;
    }

    memset(&context, 0, sizeof(ldcam_ctx_t));
    context.p_save_path = "/mnt/UDISK";
    context.i_camid = 0;

    /*
     * camera capture parameters
     */
    context.camera_config.msg_cb        = lindcamera_cb;
    context.camera_config.user          = (void*)&context;
    context.camera_config.cap_width     = 1920;
    context.camera_config.cap_height    = 1080;
    context.camera_config.framerate     = 30;
    context.camera_config.cap_width_sub = 1280;
    context.camera_config.cap_height_sub = 720;
    context.camera_config.flip          = 0;
    context.camera_config.mirror        = 0;
    context.i_ui_show                   = 1;

    /*
     * preview parameters, default full-screen
     */
    context.preview_config.device       = DISPLAY_DEVICE_NONE;
    context.preview_config.channel_id   = 0;
    context.preview_config.z_order      = 5;
    context.preview_config.rotate       = 0;
    context.preview_config.pos.left     = 0;
    context.preview_config.pos.top      = 0;
    context.preview_config.pos.width    = 0;
    context.preview_config.pos.height   = 0;

    /*
     * video encoding parameters
     */
    context.record_config.venc_type     = AV_ENC_TYPE_H264;
    context.record_config.venc_width    = context.camera_config.cap_width;
    context.record_config.venc_height   = context.camera_config.cap_height;
    context.record_config.framerate     = context.camera_config.framerate;    // same as the capture framerate
    context.record_config.bitrate       = 5*1024*1024;
    context.record_config.gop           = 30;
    context.record_config.venc_rotate   = 0;
    context.record_config.venc_mirror   = 0;
    context.record_config.rc_mode       = 0;
    context.record_config.min_qp        = 5;
    context.record_config.max_qp        = 45;
    context.record_config.profile       = 1;
    context.record_config.mux_type      = AV_MUX_TYPE_MP4;
    context.record_config.max_file_duration = 0;
    context.record_config.max_file_size = 0;

    /*
     * audio encoding parameters
     */
    context.record_config.aenc_type     = AV_ENC_TYPE_AAC;
    context.record_config.channels      = 1;
    context.record_config.sample_rate   = DEFAULT_SAMPLE_RATE;
    context.record_config.bits          = 16;

    /*
     * picture parameters
     */
    context.picture_config.width    = 3840;
    context.picture_config.height   = 2160;
    context.picture_config.quality  = 88;

    /*
     * video thumbnail parameters
     */
    context.i_thumb_w = 480;
    context.i_thumb_h = 272;

    for (i = 1; i < argc; )
    {
        if (!strcmp(argv[i], "-id"))
        {
            context.i_camid = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-path"))
        {
            context.p_save_path = argv[i + 1];
            i += 2;
        }
        else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "-cap_size"))
        {
            context.camera_config.cap_width = atoi(argv[i + 1]);
            context.camera_config.cap_height = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-ss") || !strcmp(argv[i], "-cap_size_sub"))
        {
            context.camera_config.cap_width_sub = atoi(argv[i + 1]);
            context.camera_config.cap_height_sub = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "-framerate"))
        {
            context.camera_config.framerate = atoi(argv[i + 1]);
            context.record_config.framerate = context.camera_config.framerate;
            i += 2;
        }
        else if (!strcmp(argv[i], "-vs") || !strcmp(argv[i], "-venc_size"))
        {
            context.record_config.venc_width = atoi(argv[i + 1]);
            context.record_config.venc_height = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-vt") || !strcmp(argv[i], "-venc_type"))
        {
            if (!strcmp(argv[i + 1], "h264"))
            {
                context.record_config.venc_type = AV_ENC_TYPE_H264;
            }
            else if (!strcmp(argv[i + 1], "h265"))
            {
                context.record_config.venc_type = AV_ENC_TYPE_H265;
                context.record_config.profile = 0;
            }
            else
            {
                // default use h264
                context.record_config.venc_type = AV_ENC_TYPE_H264;
            }
            i += 2;
        }
        else if (!strcmp(argv[i], "-vb") || !strcmp(argv[i], "-bitrate"))
        {
            context.record_config.bitrate = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-mt") || !strcmp(argv[i], "-mux_type"))
        {
            if (!strcmp(argv[i + 1], "mp4"))
            {
                context.record_config.mux_type = AV_MUX_TYPE_MP4;
            }
            else if (!strcmp(argv[i + 1], "ts"))
            {
                context.record_config.mux_type = AV_MUX_TYPE_MPEG_TS;
            }
            else
            {
                // default use mp4
                context.record_config.mux_type = AV_MUX_TYPE_MP4;
            }
            i += 2;
        }
        else if (!strcmp(argv[i], "-ps") || !strcmp(argv[i], "-pic_size"))
        {
            context.picture_config.width = atoi(argv[i + 1]);
            context.picture_config.height = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-ts") || !strcmp(argv[i], "-thumb_size"))
        {
            context.i_thumb_w = atoi(argv[i + 1]);
            context.i_thumb_h = atoi(argv[i + 2]);
            i += 3;
        }
        else if (!strcmp(argv[i], "-ar") || !strcmp(argv[i], "-sample_rate"))
        {
            context.record_config.sample_rate = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-dp") || !strcmp(argv[i], "-pos"))
        {
            context.preview_config.pos.left = atoi(argv[i + 1]);
            context.preview_config.pos.top = atoi(argv[i + 2]);
            context.preview_config.pos.width = atoi(argv[i + 3]);
            context.preview_config.pos.height = atoi(argv[i + 4]);
            i += 5;
        }
        else if (!strcmp(argv[i], "-dr") || !strcmp(argv[i], "-disp_rotate"))
        {
            int rotate = atoi(argv[i + 1]);
            switch(rotate)
            {
                case 0:
                    context.preview_config.rotate = ROTATE_DEGREE_NONE;
                    break;
                case 90:
                    context.preview_config.rotate = ROTATE_DEGREE_90;
                    break;
                case 180:
                    context.preview_config.rotate = ROTATE_DEGREE_180;
                    break;
                case 270:
                    context.preview_config.rotate = ROTATE_DEGREE_270;
                    break;
                default:
                    context.preview_config.rotate = ROTATE_DEGREE_NONE;
                    logw("invalid rotate(%d), force to 0", rotate);
                    break;
            }
            i += 2;
        }
        else if (!strcmp(argv[i], "-dz") || !strcmp(argv[i], "-disp_zorder"))
        {
            context.preview_config.z_order = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-dc") || !strcmp(argv[i], "-disp_channel"))
        {
            context.preview_config.channel_id = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-md"))
        {
            context.record_config.max_file_duration = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-ms"))
        {
            context.record_config.max_file_size = atoi(argv[i + 1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-ml"))
        {
            context.b_rec_loop = 1;
            i += 1;
        }
        else
        {
            i++;
        }
    }

    logd("camera parameters, id: %d, cap: %dx%d@%d(fps), "
        "venc: %s.%s, %dx%d, %d(bps), "
        "pic: %dx%d, save path: %s, "
        "preview:[%d, %d, %d, %d], rotate: %d",
        context.i_camid,
        context.camera_config.cap_width,
        context.camera_config.cap_height,
        context.record_config.framerate,
        (context.record_config.venc_type == AV_ENC_TYPE_H264) ? "h264" : "h265",
        (context.record_config.mux_type == AV_MUX_TYPE_MP4) ? "mp4" : "ts",
        context.record_config.venc_width,
        context.record_config.venc_height,
        context.record_config.bitrate,
        context.picture_config.width,
        context.picture_config.height,
        context.p_save_path,
        context.preview_config.pos.left,
        context.preview_config.pos.top,
        context.preview_config.pos.width,
        context.preview_config.pos.height,
        context.preview_config.rotate);

    sample_lindcamera(&context);

    logd("bye bye");
	return 0;
}
